package fr.utbm.to52.android_bibtex.model.spec;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import my.javax.swing.event.EventListenerList;

public class FieldSpecifications implements FieldSpecificationListener {

	private static final FieldSpecifications INSTANCE = new FieldSpecifications();

	/** key: {@link FieldSpecification#getName()}.toLowerCase() */
	private final Map<String, FieldSpecification> specifications = new HashMap<String, FieldSpecification>();

	private final EventListenerList listeners = new EventListenerList();

	public static FieldSpecifications getInstance() {
		return INSTANCE;
	}

	/** Singleton! */
	private FieldSpecifications() {
	}

	/**
	 * 
	 * @return a read-only view of the inner map.
	 */
	public Map<String, FieldSpecification> getSpecifications() {
		// Don't return a direct reference, because that would allow uncontrolled adds/removes on it
		return Collections.unmodifiableMap(this.specifications);
	}

	public FieldSpecification getSpecification(String name) {
		return this.specifications.get(name.toLowerCase());
	}

	public boolean containsSpecification(String name) {
		return this.specifications.containsKey(name.toLowerCase());
	}

	public boolean addSpecification(FieldSpecification s) {
		assert s!=null;
		assert s.getName()!=null && !s.getName().isEmpty();

		String lowerCaseName = s.getName().toLowerCase();
		if (!this.specifications.containsKey(lowerCaseName)) {
			this.specifications.put(lowerCaseName, s);
			s.addFieldSpecificationListener(this);
			fireSpecificationAdded(s);
			return true;
		}
		return false;
	}

	public boolean removeSpecification(String name) {
		assert name!=null;

		String lowerCaseName = name.toLowerCase();
		FieldSpecification s = this.specifications.get(lowerCaseName);
		if (s!=null) {
			s.removeFieldSpecificationListener(this);
			this.specifications.remove(lowerCaseName);
			fireSpecificationRemoved(s);
			return true;
		}
		return false;
	}

	public void addFieldSpecificationsListener(FieldSpecificationsListener l) {
		assert l!=null;

		this.listeners.add(FieldSpecificationsListener.class, l);
	}

	public void removeFieldSpecificationsListener(FieldSpecificationsListener l) {
		assert l!=null;

		this.listeners.remove(FieldSpecificationsListener.class, l);
	}

	public FieldSpecificationsListener[] getFieldSpecificationsListeners() {
		return this.listeners.getListeners(FieldSpecificationsListener.class);
	}

	protected void fireSpecificationAdded(FieldSpecification s) {
		assert s!=null;

		FieldSpecificationsEvent event = new FieldSpecificationsEvent(s);
		for (FieldSpecificationsListener listener : getFieldSpecificationsListeners()) {
			listener.specificationAdded(event);
		}
	}

	protected void fireSpecificationRemoved(FieldSpecification s) {
		assert s!=null;

		FieldSpecificationsEvent event = new FieldSpecificationsEvent(s);
		for (FieldSpecificationsListener listener : getFieldSpecificationsListeners()) {
			listener.specificationRemoved(event);
		}
	}

	protected void fireSpecificationChanged(FieldSpecificationEvent changeEvent) {
		assert changeEvent!=null;

		FieldSpecificationsEvent event = new FieldSpecificationsEvent(changeEvent);
		for (FieldSpecificationsListener listener : getFieldSpecificationsListeners()) {
			listener.specificationChanged(event);
		}
	}

	@Override
	public void fieldSpecificationChanged(FieldSpecificationEvent event) {
		FieldSpecification fs = event.getSource();

		assert this.specifications.containsKey(fs.getName().toLowerCase());

		if (FieldSpecification.TYPE_PROPERTY_NAME.equals(event.getPropertyName())) {
			ValueType oldType = (ValueType) event.getOldValue();
			ValueType newType = (ValueType) event.getNewValue();

			// TODO handle field specification's type change
		}

		fireSpecificationChanged(event);
	}

}
